#include <winsock2.h>

#include <ballade/network/socket.h>

namespace Ballade
{
	
	namespace Network
	{
		
		Socket::Socket()
		{
			memset(_ipaddr, 0x00, 16);
			_port = 0;
			_sock = NULL;
			_in = NULL;
			_out = NULL;
			_readTimeout = 1000;
			_writeTimeout = 1000;
			_isConnected = false;
		}
		
		Socket::Socket(const char* ipaddr, const unsigned short port)
		{
			setIPAddress(ipaddr);
			setPort(port);
			_sock = NULL;
			_in = NULL;
			_out = NULL;
			_readTimeout = 1000;
			_writeTimeout = 1000;
			_isConnected = false;
		}
		
		Socket::~Socket()
		{
		}
		
		const InputStream &Socket::getInputStream() const { return *_in; }
		const OutputStream &Socket::getOutputStream() const { return *_out; }
		const int Socket::getSocketDescriptor() const { return _sock; }
		
		void Socket::setIPAddress(const char *ipaddr)
		{ 
			memset(_ipaddr, 0x00, sizeof(_ipaddr));
			int len = strlen(ipaddr);
			len = len > sizeof(_ipaddr) ? sizeof(_ipaddr) - 1 : len;
			memcpy(_ipaddr, ipaddr, len);
		}
		
		void Socket::setPort(const unsigned short port) { _port = port; }
		
		void Socket::setReadTimeout(const int val)
		{
			if(_sock > 0) setsockopt(_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&val, sizeof(int));
			_readTimeout = val;
		}
		
		void Socket::setWriteTimeout(const int val)
		{
			if(_sock > 0) setsockopt(_sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&val, sizeof(int));
			_writeTimeout = val;
		}
		
		const char* Socket::getIPAddress() const { return _ipaddr; }
		
		const unsigned short& Socket::getPort() const { return _port; }
		
		const int& Socket::getReadTimeout() const { return _readTimeout; }
		
		const int& Socket::getWriteTimeout() const { return _writeTimeout; }
		
		const bool& Socket::isConnected() const
		{
			return _isConnected;
		}
		
		void Socket::setInputStream(InputStream *in)
		{
		 	_in = in;
		}
		
		void Socket::setOutputStream(OutputStream *out)
		{
		 	_out = out;
		}
		
		void Socket::connect(const char *ipaddr, const unsigned short port)
		{
			setIPAddress(ipaddr);
			setPort(port);
			connect();
		}
		
		void Socket::connect()
		{
			if(isConnected()) close();

			WSADATA wsaData;
			if(::WSAStartup(0x202, &wsaData) != 0)
			{
				WSACleanup();
				throw SocketError("WSAStartup failed");
			};
			
			_sock = socket(AF_INET, SOCK_STREAM, 0);
			if(_sock < 0) throw SocketError("Create socket failed");
			
			setReadTimeout(_readTimeout);
			setWriteTimeout(_writeTimeout);
			
			struct sockaddr_in sa;
			memset(&sa, 0x00, sizeof(sockaddr_in));
			sa.sin_family = AF_INET;
			sa.sin_addr.s_addr = inet_addr(_ipaddr);
			sa.sin_port = htons(_port);
			
			int err = ::connect(_sock, (struct sockaddr *)&sa, sizeof(sa));
			if(err < 0) throw SocketError("Connect failed");
			
			_in = new InputStream(_sock);
			_out = new OutputStream(_sock);
			_isConnected = true;
		}
		
		void Socket::close()
		{
			if(_sock) 
			{
				closesocket(_sock);
				_sock = NULL;
			}
			_isConnected = false;

			if(_in) delete _in;
			if(_out) delete _out;	

			WSACleanup();
		}
		
	}

}
